import { children, createComputed, createEffect, createSignal, onCleanup, onMount } from "solid-js"
import { Dynamic } from "solid-js/web"

export interface VirtualListCoreProps {
    height?: number,
    maxHeight?: number,
    itemEstimatedSize: number
    children: any,
    overscan?: number,
    items: any[],
    scrollElement: HTMLDivElement,
    contentElement: HTMLDivElement,
    bodyElement: HTMLDivElement,
}

export interface MeasuredData {
    size: number,
    offset: number,
}
export interface IMeasuredDataMap {
    [key: number]: MeasuredData
}

export function VirtualListCore(props: VirtualListCoreProps) {
    const containerHeight = props.height ?? props.maxHeight;
    if (containerHeight === undefined) {
        console.error('Virtual List need height or maxHeight prop');
    }

    const [scrollOffset, setScrollOffset] = createSignal(0);
    const [start, setStart] = createSignal(0);
    let wrap = props.scrollElement;
    let content = props.bodyElement;
    let measuredDataMap: IMeasuredDataMap = {};
    // 计算每项的高度和offset
    const measures = () => {
        measuredDataMap = {};
        const count = props.items.length;
        for (let i = 0; i < count; i++) {
            const prevItem = measuredDataMap[i - 1];
            const offset = prevItem ? prevItem.offset + prevItem.size : 0;
            measuredDataMap[i] = { size: props.itemEstimatedSize, offset };
        }
    }

    measures();

    // 二分法搜索
    const binarySearch = ({ low, high, scrollOffset }: any) => {
        let middle = 0;
        let currentOffset = 0;
        while (low <= high) {
            middle = low + Math.floor((high - low) / 2);
            currentOffset = measuredDataMap[middle]?.offset;
            if (currentOffset === scrollOffset) {
                return middle;
            } else if (currentOffset < scrollOffset) {
                low = middle + 1;
            } else {
                high = middle - 1
            }
        }
        if (low > 0) {
            return low - 1;
        }
        return 0;
    }

    // 指数搜索
    const exponentialSearch = (scrollOffset: number) => {
        const itemCount = props.items.length;
        let interval = 1;
        let index = 0;
        while (index < itemCount && measuredDataMap[index]?.offset < scrollOffset) {
            index += interval;
            interval *= 2;
        }
        return binarySearch({
            low: Math.floor(index / 2),
            high: Math.min(index, itemCount - 1),
            scrollOffset
        })
    }

    // 获取第一个索引
    const getStartIndex = (scrollOffset: number) => {
        return exponentialSearch(scrollOffset);
    }

    // 获取容器最后一个索引
    const getEndIndex = (startIndex: number) => {
        const itemCount = props.items.length;
        if (itemCount === 0) {
            return 0;
        }
        // 获取可视区内开始的项
        const startItem = measuredDataMap[startIndex];
        // 可视区内最大的offset值
        const maxOffset = startItem?.offset + (props.scrollElement.clientHeight ?? 0);
        // 开始项的下一项的offset，之后不断累加此offset，直到等于或超过最大offset，就是找到结束索引了
        let offset = startItem?.offset + startItem?.size;
        // 结束索引
        let endIndex = startIndex;
        // 累加offset
        while (offset <= maxOffset && endIndex < (itemCount - 1)) {
            endIndex++;
            const currentItem = measuredDataMap[endIndex];
            if (currentItem) {
                offset += currentItem.size;
            }
        }
        return endIndex;
    };

    // 计算渲染的范围
    const getRangeToRender = (scrollOffset: number) => {
        const itemCount = props.items.length;
        if (itemCount === 0) {
            return [-1, -1];
        }
        const startIndex = getStartIndex(scrollOffset);
        const endIndex = getEndIndex(startIndex);
        return [
            Math.max(0, startIndex - (props.overscan || 3)),
            Math.min(itemCount - 1, endIndex + (props.overscan || 3)),
            startIndex,
            endIndex,
        ];
    };

    const estimatedHeight = () => {
        const itemCount = props.items.length;
        let h = 0;
        for (let i = 0; i < itemCount; i++) {
            h += measuredDataMap[i]?.size;
        }
        return h;
    }

    const [height, setHeight] = createSignal(estimatedHeight());

    createComputed(() => {
        let originHeight: number | undefined = props.height;
        if (props.maxHeight) {
            originHeight = height() > props.maxHeight ? props.maxHeight : height();
        }
        props.scrollElement.style.height = originHeight + 'px';
    })

    createEffect(() => {
        props.contentElement.style.height = height() + 'px';
    })

    createEffect(() => {
        props.bodyElement.style.transform = `translateY(${measuredDataMap[start()]?.offset}px)`;
    })

    const scrollHandle = (event: any) => {
        const { scrollTop } = event.target;
        setScrollOffset(scrollTop);
    }

    // 子元素项尺寸计算并重新计算offset
    const measureElement = (el: Element, index: number) => {
        const rect = el.getBoundingClientRect();
        const item = measuredDataMap[index];
        if (item && item.size === rect.height) {
            return;
        }
        if (item) {
            item.size = rect.height;
        }
        const itemCount = props.items.length;
        for (let i = index + 1; i < itemCount; i++) {
            const item = measuredDataMap[i];
            const prevItem = measuredDataMap[i - 1];
            item.offset = prevItem ? prevItem.offset + prevItem.size : 0;
        }

        setHeight(estimatedHeight());
    }

    // 数据源更改触发
    createEffect(() => {
        props.items;
        measures();
        wrap.scrollTop = 0;
        setHeight(estimatedHeight());
    });

    // 子元素
    const getCurrentChildren = children(() => {
        const [startIndex, endIndex] = getRangeToRender(scrollOffset());
        setStart(startIndex);

        const items = [];
        if (startIndex >= 0) {
            for (let i = startIndex; i <= endIndex; i++) {
                const itemStyle = {
                    width: '100%',
                };
                items.push(
                    <Dynamic component={props.children} ref={(el: Element) => {
                        Promise.resolve().then(() => {
                            measureElement(el, i);
                        })
                    }}
                        items={props.items} item={props.items[i]} index={i} meta={measuredDataMap[i]} style={itemStyle} />
                );
            }
        }
        return items;
    })

    // 容器尺寸变化时重新计算当前可视区域元素的尺寸，触发高度变化，之后重新触发scroll变化重新计算可视区域索引,刷新可视区域元素
    const onWrapEntry = async (entry: ResizeObserverEntry) => {
        const [startIndex, endIndex] = getRangeToRender(scrollOffset());
        const childs = content.children;
        for (let i = startIndex; i <= endIndex; i++) {
            const el = childs[i - startIndex];
            if (el) {
                measureElement(el, i);
            }
        }
        Promise.resolve().then(() => {
            setScrollOffset(scrollOffset() + 1);
        })
    }
    const ro = new ResizeObserver((entries) => {
        entries.forEach((entry) => onWrapEntry(entry));
    });

    onMount(() => {
        // 容器尺寸变化
        ro.observe(wrap);
        onCleanup(() => {
            ro.unobserve(wrap);
        });

        // 列表元素大小变化时，导致容器高度不够或超长问题
        ro.observe(props.bodyElement);
        onCleanup(() => {
            ro.unobserve(props.bodyElement);
        });

        props.scrollElement.addEventListener('scroll', scrollHandle, false);
        onCleanup(() => {
            props.scrollElement.removeEventListener('scroll', scrollHandle, false);
        })
    });

    return <>
        {getCurrentChildren()}
    </>
}